package com.github.lh

import java.security.PrivateKey
import java.security.PublicKey


class Transaction(
        val sender: PublicKey,
        val receiver: PublicKey,
        val value: Float,
        val inputs: ArrayList<TransactionInput>
) {
    lateinit var transactionId: String
    lateinit var signature: ByteArray
    var sequence: Int = 0

    val outputs: ArrayList<TransactionOutput> = arrayListOf()

    fun getInputsValue(): Float = inputs.mapNotNull { it.UTXO?.value }
            .let {
                if (it.isEmpty()) return 0f
                else it.reduce { a, b -> a + b }
            }

    fun getOutputsValue(): Float = outputs.map { it.value }
            .let {
                if (it.isEmpty()) return 0f
                else it.reduce { a, b -> a + b }
            }

    fun processTransaction(): Pair<Boolean, String> {
        if (!verifySignature()) return false to "Transaction signature is invalid"

        inputs.onEach { it.UTXO = Chain.UTXOs[it.transactionOutputId] }

        val leftOver = getInputsValue().run {
            if (this < Chain.minimumTransaction) return false to "Transaction inputs too small: $this"
            this - value
        }

        transactionId = calculateHash()
        outputs.add(TransactionOutput(receiver, value, transactionId))
        outputs.add(TransactionOutput(sender, leftOver, transactionId))
        outputs.forEach { Chain.UTXOs[it.id] = it }

        inputs.forEach { it.UTXO?.run { Chain.UTXOs.remove(id) } }
        return true to "Success"
    }

    fun generateSignature(privateKey: PrivateKey) {
        signature = applyECDSASig(privateKey, "${getStringFromKey(sender)}${getStringFromKey(receiver)}$value")
    }

    fun verifySignature(): Boolean =
            verifyECDSASig(sender,
                    "${getStringFromKey(sender)}${getStringFromKey(receiver)}$value",
                    signature)

    fun calculateHash(): String =
            applySha256("${getStringFromKey(sender)}${getStringFromKey(receiver)}$value$sequence")

}

class TransactionInput(
        val transactionOutputId: String,
        var UTXO: TransactionOutput? = null
)

class TransactionOutput(
        val receiver: PublicKey,
        val value: Float,
        val parentTransactionId: String
) {
    val id: String = applySha256("${getStringFromKey(receiver)}$value$parentTransactionId")

    //Check if coin belongs to you
    fun isMine(publicKey: PublicKey): Boolean = publicKey === receiver
}